﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;

/*=====================================================================
  This file is part of a Microsoft SQL Server Shared Source Application.
  Copyright (C) Microsoft Corporation.  All rights reserved.
 
THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
======================================================= */


namespace Microsoft.SqlCommunity.FaultRetry
{
    /// <summary>
    /// Part of the implementation of a custom .NET Framework Data Provider which wraps the SQL Server data provider.
    /// This custom data provider  retries operations which 
    /// fail to enable automatic failover to a mirror server.  
    /// 
    /// This class encapsulates a SqlDataReader and ensures that if certain exceptions are raised that retry logic is invoked
    /// while data is being streamed from the database.  All operations eventually are passed through to the underlying SqlDataReader.
    /// </summary>
    public class FaultRetryDataReader : IDataReader
    {
        SqlDataReader _reader;  //The underlying SqlDataReader
        FaultRetryConnection _connection;  //The object that represents the path to the database on the principal/mirror pair.

        /// <summary>
        /// Create a data reader initializing the underlying data reader and path to the database on the principal/mirror pair.
        /// </summary>
        /// <param name="reader">The underlying SqlDataReader which does the actual work.</param>
        /// <param name="connection">The object that represents the path to the database on the principal/mirror pair.</param>
        internal FaultRetryDataReader(SqlDataReader reader, FaultRetryConnection connection)
        {
            _reader = reader;
            _connection = connection;
        }


        //
        // Methods and properties from IDataReader.
        //

        /// <summary>
        /// Returns the depth of nesting for hierarchical data
        /// </summary>
        public int Depth
        {
            get { return _reader.Depth; }
        }

        /// <summary>
        /// True if data is available
        /// </summary>
        public bool IsClosed
        {
            get { return _reader.IsClosed; }
        }

        /// <summary>
        /// Returns the number of rows impacted by the command
        /// </summary>
        public int RecordsAffected
        {
            get { return _reader.RecordsAffected; }
        }

        /// <summary>
        /// Used to indicate that the reader isn't needed anymore.
        /// </summary>
        public void Close()
        {
            // Close the underlying reader.
            _reader.Close();
        }

        /// <summary>
        /// Get the next result set from the set of results returned from the query.
        /// </summary>
        /// <returns>True if another result set is available</returns>
        public bool NextResult()
        {
            return _connection.WithMirrorRetry<bool>(() => _reader.NextResult());
        }

        /// <summary>
        /// Try and read the next row from the current result set.
        /// </summary>
        /// <returns>True if there is a valid row available.</returns>
        public bool Read()
        {
            return _connection.WithMirrorRetry<bool>(() => _reader.Read());
        }

        /// <summary>
        /// Get schema information about the data being returned for the current result set.
        /// </summary>
        /// <returns>A data table containing schema information</returns>
        public DataTable GetSchemaTable()
        {
            return _connection.WithMirrorRetry<DataTable>(() => _reader.GetSchemaTable());
        }

        // Methods and properties from IDataRecord.
         
        /// <summary>
        /// Returns the number of columns.
        /// </summary>
        public int FieldCount
        {
            get { return  _reader.FieldCount; }
        }

        /// <summary>
        /// Returns the name of the specified column
        /// </summary>
        /// <param name="i">Which column</param>
        /// <returns>The name of the column</returns>
        public String GetName(int i)
        {
            return _reader.GetName(i);
        }

        /// <summary>
        /// Returns the backend type name for the column.
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The name of the data type of the column</returns>
        public String GetDataTypeName(int i)
        {
            return _reader.GetDataTypeName(i);
        }

        /// <summary>
        /// Returns the CLR type object for the data in the specific column.
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The type object for the column</returns>
        public Type GetFieldType(int i)
        {
            // Return the actual Type class for the data type.
            return _reader.GetFieldType(i);
        }

        /// <summary>
        /// Returns the native data of the column
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The native data</returns>
        public Object GetValue(int i)
        {
            return _reader.GetValue(i);
        }

        /// <summary>
        /// Return all the native data in the row as an array of objects
        /// </summary>
        /// <param name="values">The array to fill</param>
        public int GetValues(object[] values)
        {
            return _reader.GetValues(values);
        }

        /// <summary>
        /// Returns the position for the column given the name of the column
        /// </summary>
        /// <param name="name">The name of the column</param>
        /// <returns>The position for the column</returns>
        public int GetOrdinal(string name)
        {
            return _reader.GetOrdinal(name);
        }

        /// <summary>
        /// Returns the native value of the specified column.
        /// </summary>
        /// <param name="i">The position of the column.</param>
        /// <returns>The native data value of the column.</returns>
        public object this[int i]
        {
            get { return _reader[i]; }
        }

        /// <summary>
        /// Returns the native value of the specific column.
        /// </summary>
        /// <param name="name">The name of the column.</param>
        /// <returns>The native data value of the column.</returns>
        public object this[String name]
        {
            get { return _reader[name]; }
        }

        /// <summary>
        /// Returns the value of the specific column as a boolean.
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The boolean value of the column</returns>
        public bool GetBoolean(int i)
        {
            return _reader.GetBoolean(i);
        }

        /// <summary>
        /// Returns the value of the specific column as a byte.
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The byte value of the column</returns>
        public byte GetByte(int i)
        {
            return _reader.GetByte(i);
        }

        /// <summary>
        /// Reads a series of bytes from the specified column and places them at the requested spot in a buffer.
        /// </summary>
        /// <param name="i">The column containing the bytes.</param>
        /// <param name="fieldOffset">Where to start reading in the column<./param>
        /// <param name="buffer">Where to place the bytes.</param>
        /// <param name="bufferOffset">At what location in the buffer to start placing the bytes.</param>
        /// <param name="length">How many bytes to read.</param>
        /// <returns></returns>
        public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferOffset, int length)
        {
            return _reader.GetBytes(i, fieldOffset, buffer, bufferOffset, length);
        }

        /// <summary>
        /// Returns the value of the specific column as a character.
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The character value of the column</returns>
        public char GetChar(int i)
        {
            return ((IDataReader)_reader).GetChar(i);
        }

        /// <summary>
        /// Reads a series of characters into a buffer from the specified column.
        /// </summary>
        /// <param name="i">The column containing characters</param>
        /// <param name="fieldOffset">Where to start reading from the column.</param>
        /// <param name="buffer">The buffer to place the characters into.</param>
        /// <param name="bufferOffset">Where to start placing the characters in the buffer.</param>
        /// <param name="length">How many characters to read.</param>
        /// <returns></returns>
        public long GetChars(int i, long fieldOffset, char[] buffer, int bufferOffset, int length)
        {
            return _reader.GetChars(i, fieldOffset, buffer, bufferOffset, length);
        }

        /// <summary>
        /// Returns the value of the specific column as a Guid.
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The Guid value of the column</returns>
        public Guid GetGuid(int i)
        {
            return _reader.GetGuid(i);
        }

        /// <summary>
        /// Returns the value of the specific column as a short.
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The short value of the column</returns>
        public Int16 GetInt16(int i)
        { 
            return _reader.GetInt16(i);
        }

        /// <summary>
        /// Returns the value of the specific column as a int.
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The int value of the column</returns>
        public Int32 GetInt32(int i)
        {
            return _reader.GetInt32(i);
        }

        /// <summary>
        /// Returns the value of the specific column as a long.
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The long value of the column</returns>
        public Int64 GetInt64(int i)
        {
            return _reader.GetInt64(i);
        }

        /// <summary>
        /// Returns the value of the specific column as a float.
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The float value of the column</returns>
        public float GetFloat(int i)
        {
            return _reader.GetFloat(i);
        }

        /// <summary>
        /// Returns the value of the specific column as a double.
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The double value of the column</returns>
        public double GetDouble(int i)
        {
            return _reader.GetDouble(i);
        }

        /// <summary>
        /// Returns the value of the specific column as a string.
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The string value of the column</returns>
        public String GetString(int i)
        {
            return _reader.GetString(i);
        }

        /// <summary>
        /// Returns the value of the specific column as a Decimal.
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The Decimal value of the column</returns>
        public Decimal GetDecimal(int i)
        {
            return _reader.GetDecimal(i);
        }

        /// <summary>
        /// Returns the value of the specific column as a DateTime.
        /// </summary>
        /// <param name="i">The column</param>
        /// <returns>The DateTime value of the column</returns>
        public DateTime GetDateTime(int i)
        {
            return _reader.GetDateTime(i);
        }

        /// <summary>
        /// Returns a stream of data for hierarchical data contained within the specific column.
        /// </summary>
        /// <param name="i">The column.</param>
        /// <returns>The stream of data as a data reader.</returns>
        public IDataReader GetData(int i)
        {
            return ((IDataReader)_reader).GetData(i);
        }

        /// <summary>
        /// Determines if the value in the column is null.
        /// </summary>
        /// <param name="i">The column.</param>
        /// <returns>True if the value in the column is null.</returns>
        public bool IsDBNull(int i)
        {
            return _reader.IsDBNull(i);
        }


        /// <summary>
        /// Implement the standard dispose pattern.
        /// </summary>
        void IDisposable.Dispose()
        {
            this.Dispose(true);
            System.GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Implement the standard dispose pattern.
        /// </summary>
        /// <param name="disposing"></param>
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                try
                {
                    this.Close();
                }
                catch (Exception e)
                {
                    //TODO: Maybe pick a better exception to throw, and add an inner exception.
                    throw new SystemException("An exception of type " + e.GetType() +
                                              " was encountered while closing the MirrorDataReader.");
                }
            }
        }

    }
}
